Hands-on Exercise 3

Published

June 3, 2023

Load and Install R Packages

code block
pacman::p_load(ggiraph, plotly, gganimate,
               gifski, gapminder,
               DT, tidyverse, patchwork, readxl,
               urbnthemes, ggthemr, wesanderson)

Importing the data

code block
exam_data <- read_csv("data/Exam_data.csv")

Interactive Data Visualisation

ggiraph package

Interactive Arguments of ggiraph

  • Tooltip: displayed when hovering over chart elements
  • Onclick: executed when elements are clicked
  • Data_id: Uses columns as id to show association of elements

1: Tooltips

Original ggplot dotplot:

code block
set_urbn_defaults(style = "print")

ggplot(data = exam_data,
       aes(x = MATHS)) +
  geom_dotplot(stackdir = "up", 
               binwidth = 1,
               dotsize = 1) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(x = "Math Scores") + 
  theme(axis.title.y = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank())

With ggiraph tooltips:

code block
p <- ggplot(data = exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = ID),
    stackgroups = TRUE, 
    binwidth = 1,
    dotsize = 1,
    method = "histodot") +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(x = "Math Scores")

girafe(
  ggobj = p,
  width_svg = 6,
  height_svg = 6*0.618
)

1.1: Controlling Information in Tooltips

Customise information to be included in tooltips by creating a new list

code block
exam_data$tooltip <- c(paste0(
  "Name:", exam_data$ID,
  "\n Class:", exam_data$CLASS
))

p <- ggplot(data = exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = exam_data$tooltip),
    stackgroups = TRUE, 
    binwidth = 1,
    dotsize = 1,
    method = "histodot") +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(x = "Math Scores")

girafe(
  ggobj = p,
  width_svg = 6,
  height_svg = 6*0.618
)

1.2: Formatting style of Tooltips

Adding css arguments to opts_tooltip() of ggiraph

code block
tooltip_css <- "background-color:#dfdfeb; font-style:bold; color:black;"  #<<

exam_data$tooltip <- c(paste0(
  "Name:", exam_data$ID,
  "\n Class:", exam_data$CLASS
))

p <- ggplot(data = exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = exam_data$tooltip),
    stackgroups = TRUE, 
    binwidth = 1,
    dotsize = 1,
    method = "histodot") +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(x = "Math Scores")

girafe(
  ggobj = p,
  width_svg = 6,
  height_svg = 6*0.618,
  options = list(    #<<
    opts_tooltip(    #<<
      css = tooltip_css)) #<<
)                               

1.3: Displaying Statistics in tooltips

Adding stat_summary() calculations in ggplot

code block
ggthemr("flat")

tooltip <- function(y, ymax, accuracy = .01) {   #<<
  mean <- scales::number(y, accuracy = accuracy) #<<
  sem <- scales::number(ymax - y, accuracy = accuracy) #<<
  paste("Mean Math Score:", mean, "+/-", sem) #<<
} #<<

p2 <- ggplot(data=exam_data, 
                   aes(x = RACE)) +
  stat_summary(aes(y = MATHS, 
                  tooltip = after_stat(tooltip(y, ymax))),
                  fun.data ="mean_se",
                  geom = GeomInteractiveCol,
                  fill = "light blue") +
  stat_summary(aes(y = MATHS),
               fun.data = mean_se,
               geom ="errorbar", 
               width = 0.2, linewidth = 0.2) +
  labs(y ="Math Scores") + 
  theme(axis.title.x = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(ggobj = p2,
       width_svg = 8,
       height_svg = 8*0.618)

2: Hover effect with data_id

data_id is specified as an aes() argument in the interactive geom functions

e.g: geom_dotplot_interactive(aes(data_id = variablename))

code block
p <- ggplot(data = exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(           
    aes(data_id = CLASS),             #<<
    stackgroups = TRUE,               
    binwidth = 1,                        
    method = "histodot") +               
  scale_y_continuous(NULL,               
                     breaks = NULL) + 
  labs(x ="Math Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(                                  
  ggobj = p,                             
  width_svg = 6,                         
  height_svg = 6*0.618                      
)        

2.1: Formatting Style of Hover Effect

Customisation Options

  • opts_hover(css = NULL, reactive = FALSE, nearest_distance = NULL) > effect on geometries
  • opts_hover_inv(css = NULL) > effect on other geometries, while one is hovered
  • opts_hover_key(css = NULL, reactive = FALSE) > interactive scales
  • opts_hover_theme(css = NULL, reactive = FALSE) > interactive theme elements
code block
p <- ggplot(data = exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(           
    aes(data_id = CLASS),             
    stackgroups = TRUE,               
    binwidth = 1,                        
    method = "histodot") +               
  scale_y_continuous(NULL,               
                     breaks = NULL) + 
  labs(x ="Math Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(                                  
  ggobj = p,                             
  width_svg = 6,                         
  height_svg = 6*0.618,
  options = list(                     #<<
  opts_hover(css = "fill: #202020;"), #<<
  opts_hover_inv(css = "opacity:0.2;") #<<
  )                                   #<<  
) 

1 + 2: Tooltip + Hover Powers Combined!!

code block
p <- ggplot(data=exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(              
    aes(tooltip = CLASS, #<<
        data_id = CLASS),#<<              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") +               
  scale_y_continuous(NULL,               
                     breaks = NULL) +
  labs(x ="Math Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(                                  
  ggobj = p,                             
  width_svg = 6,                         
  height_svg = 6*0.618,
  options = list(                        
    opts_hover(css = "fill: #202020;"),  
    opts_hover_inv(css = "opacity:0.2;") 
  )                                        
)                                        

2.2: Coordinated Multiple Views

When a data point of one of the dotplot is selected, the corresponding data point ID on the second data visualisation will be highlighted too

  • Use interactive functions of ggiraph to create multiple views
  • Patchwork function from patchwork package weaves views together as coordinated plots
code block
p1 <- ggplot(data=exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(              
    aes(tooltip = CLASS, 
        data_id = ID),              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") +  
  coord_cartesian(xlim=c(0,100)) + #<<
  scale_y_continuous(NULL,               
                     breaks = NULL) +
  labs(x ="Math Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

p2 <- ggplot(data=exam_data, 
       aes(x = ENGLISH)) +
  geom_dotplot_interactive(              
    aes(tooltip = CLASS,
        data_id = ID),              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") + 
  coord_cartesian(xlim=c(0,100)) + #<<
  scale_y_continuous(NULL,               
                     breaks = NULL) +
  labs(x ="English Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(code = print(p1 / p2), #<<
       width_svg = 6,
       height_svg = 6,
       options = list(
         opts_hover(css = "fill: #202020;"),
         opts_hover_inv(css = "opacity:0.2;")
         )
       ) 

3: Onlick

onclick argument opens link up in new window when clicked.

code block
tt <- "click me"

exam_data$onclick <- sprintf("window.open(\"%s%s\")",
"https://www.onemap.gov.sg/main/v2/schoolquery",
as.character(exam_data$ID))

p <- ggplot(data=exam_data,
            aes(x = MATHS)) +
  geom_dotplot_interactive(aes(tooltip = tt,
                               onclick = exam_data$onclick),
                           stackgroups = TRUE,
                           binwidth = 1,
                           method = "histodot") +               
  scale_y_continuous(NULL,               
                     breaks = NULL) +
  labs(x ="Math Scores") + 
  theme(axis.line = element_blank(),
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

girafe(                                  
  ggobj = p,                             
  width_svg = 6,                         
  height_svg = 6*0.618)                                        

plotly package

Create interactive graphs using:

  • plot_ly()
  • ggplotly()

3.1: Using plot_ly()

code block
plot_ly(data = exam_data, 
             x = ~MATHS, 
             y = ~ENGLISH)

3.1.2: Adding Visual Variables

Adding color argument to code chunk to vary marker colors by specified category

code block
plot_ly(data = exam_data, 
        x = ~ENGLISH,
        y = ~MATHS,
        color = ~RACE) 

3.2: Using ggplotly()

code block
moonrise_cols <- wes_palette("Moonrise2", type = "discrete")

p <- ggplot(data = exam_data,
            aes(x = ENGLISH,
                y = MATHS,
                color = RACE,
                text = paste("<b>English Score:</b>", ENGLISH, "<br><b>Math Score:</b>", MATHS,"<br>Race:", RACE))) +
      geom_point(size = 1.5) +
      labs(title = "Math and English Scores by Race",
           x = "English Score",
           y = "Math Score") +
      scale_color_manual(values = wes_palette("Moonrise2", n = 4)) + 
      theme_grey() +
      theme(plot.title = element_text(color = moonrise_cols[4], size = 14, face = "bold"),
            axis.title.x = element_text(color = moonrise_cols[1], size = 10, face = "bold"),
            axis.title.y = element_text(color = moonrise_cols[1], size = 10, face = "bold"),
            plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6")
           )

ggplotly(p, tooltip = "text")

3.2.2: Coordinated Multiple views with ggplotly

This involves three steps:

  • highlight_key() of plotly package is used as shared data
  • create scatterplots using ggplot2 functions
  • subplot() of plotly package is used to place them side-by-side
code block
d <- highlight_key(exam_data)

p1 <- ggplot(data = d, 
            aes(x = MATHS,
                y = ENGLISH,
                text = paste("<b>Math Score:</b>", MATHS,"<br><b>English Score:", ENGLISH))) +
  geom_point(size = 1, shape = 18) +
  coord_cartesian(xlim = c(0,100),
                  ylim = c(0,100)) + 
  theme(plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

p2 <- ggplot(data = d, 
            aes(x = MATHS,
                y = SCIENCE,
                text = paste("<b>Math Score:</b>", MATHS,"<br><b>Science Score:", SCIENCE))) +
  geom_point(size = 1) +
  coord_cartesian(xlim = c(0,100),
                  ylim = c(0,100)) +
  theme(plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

subplot(ggplotly(p1, tooltip = "text"),
        ggplotly(p2, tooltip = "text"))

Crosstalk and DT

Crosstalk is an add-on to the htmlwidgets package. It extends htmlwidgets with a set of classes, functions, and conventions for implementing cross-widget interactions (currently, linked brushing and filtering).

Limitations of Crosstalk:

  • HTML widgets must be specifically modified to support Crosstalk
  • Only works for linked brushing and filtering of views that show individual data points, not aggregate or summary views
  • Because all data must be loaded into the browser, Crosstalk is not appropriate for large data sets

4.1: DataTables using DT package

DT package allow rendering of data objects as HTML tables

code block
DT::datatable(exam_data[c("ID","CLASS","GENDER","RACE","ENGLISH","MATHS","SCIENCE")],
              caption = htmltools::tags$caption(style = "caption-side: bottom; text-align: center;",
                                                "Table 1: ", htmltools::em("Exam Data for Primary 3")),
              class = "compact")

4.2: Linking Plot to Table

code block
exam_data <- subset(exam_data, select = c(1:7))
d <- highlight_key(exam_data)

p <- ggplot(data = d, 
            aes(x = MATHS,
                y = ENGLISH,
                text = paste("<b>Math Score:</b>", MATHS,"<br><b>English Score:", ENGLISH))) +
  geom_point(size = 1) +
  coord_cartesian(xlim = c(0,100),
                  ylim = c(0,100)) + 
  theme(plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

gg <- highlight(ggplotly(p, tooltip = "text"),        
                "plotly_selected")  

crosstalk::bscols(gg,               
                  DT::datatable(d), 
                  widths = 5)  

gganimate and gifski packages

gganimate provides a range of new grammar classes that can be added to the plot object in order to customise how it should change with time.

  • transition_*() defines how the data should be spread out and how it relates to itself across time
  • view_*() defines how the positional scales should change along the animation
  • shadow_*() defines how data from other points in time should be presented in the given point in time
  • enter_*()/exit_*() defines how new data should appear and how old data should disappear during the course of the animation
  • ease_aes() defines how different aesthetics should be eased during transitions

gifski converts video frames to GIF animations

*Importing Data**

code block
col <- c("Ethnicity")

msia_pop <- read_csv("data/Malaysia_demographics.csv") %>%
  mutate(Year = as.integer(Year))

5.1: Animated Bubble Plot 1

This involves three steps:

  • Create a static Bubble Plot using ggplot
  • transition_time() of gganimate is used to create transition through distinct states in time (i.e. Year)
  • ease_aes() is used to control easing of aesthetics. The default is linear. Other methods are: quadratic, cubic, quartic, quintic, sine, circular, exponential, elastic, back, and bounce
code block
ggplot(msia_pop, aes(x = Pct_Old, y = Pct_Young, 
                      size = Population, 
                      colour = Ethnicity)) +
  geom_point(alpha = 0.7) +
  scale_colour_manual(values = wes_palette("Darjeeling1")) +
  scale_size(range = c(5, 25)) +
  labs(title = 'Malaysian Population by Ethnicity for Year: {frame_time}',
       subtitle = "Data Source: https://www.kaggle.com/datasets/jasonkwm/malaysia-demographic-20102019",
       x = '% Aged', 
       y = '% Young') +
  guides(size = "none") +
  theme(legend.position = "bottom",
        plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6")) + 
  transition_time(Year) +       #<<
  ease_aes('linear')    

5.2: Animated Bubble Plot 2

*Importing data**

code block
globalPop <- read_xls("data/GlobalPopulation.xls",
                      sheet="Data") %>%
  mutate(across(where(is.character), as.factor)) %>%
  mutate(Year = as.integer(Year))
code block
ggplot(globalPop, aes(x = Old, y = Young, 
                      size = Population, 
                      colour = Country)) +
  geom_point(alpha = 0.7, 
             show.legend = FALSE) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  labs(title = 'Year: {frame_time}', 
       x = '% Aged', 
       y = '% Young') +
  theme_gray() + 
  theme(plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6")) + 
  transition_time(Year) +       
  ease_aes('linear')          

Plotly Frame Animations

6.1: ggplotly Animated Bubble Plot

ggplotly() is used to convert the R graphic object into an animated svg object.

code block
gg <- ggplot(globalPop, 
       aes(x = Old, 
           y = Young, 
           size = Population, 
           colour = Country)) +
  geom_point(aes(size = Population,
                 frame = Year),  #<<
             alpha = 0.7, 
             show.legend = FALSE) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  labs(x = '% Aged', 
       y = '% Young') + 
  theme_gray() + 
  theme(plot.background = element_rect(fill = "#F8F3E6", color = "#F8F3E6"))

ggplotly(gg)

6.2: plot_ly() Animated Bubble Plot

code block
bp <- globalPop %>%
  plot_ly(x = ~Old, 
          y = ~Young, 
          size = ~Population, 
          color = ~Continent, 
          frame = ~Year, 
          text = ~Country, 
          hoverinfo = "text",
          type = 'scatter',
          mode = 'markers'
          )
bp